/**
 * 数据劫持操作：检测用户传递进来对象，将每个属性进行劫持。
 * 将得到对象属性，挂载this（vue实例）身上
 */
class Vue{
    constructor(options){
        console.log("constructor");
        this.$options = options
        this.$el = options.el
        this.$data = options.data()
        //数据劫持$data对象，以后你修改属性，本质上$data
        new Observer(this.$data)
        //数据劫持Vue实例对象，给app动态添加了data中属性
        this.proxy()
        //模板编译
        new Compiler(this.$el,this.$data)
    }
    //专门针对你传递进来的data数据进行数据劫持
    proxy(){
        Object.keys(this.$data).forEach(key=>{
            Object.defineProperty(this,key,{
                get(){
                    return this.$data[key]
                },
                set(val){
                    this.$data[key] = val;
                }
            })
        })
    }
}
/**
 * 专门针对$data这个数据进行数据劫持
 */
class Observer{
    constructor(data){
        this.data = data
        this.walk()
    }
    //获取数据，来自于$data 修改数据也是修改$data
    defineReative(data,key,value){
        const dep = new Dep()
        Object.defineProperty(data,key,{
            get(){
                //依赖收集：我要判断页面上哪些标签用到{{}}
                //当我发现某一个属性被页面使用，Watcher存放Dep中
                //页面中 console.log(app.msg)  {{msg}}
                console.log("$data");
                if(Dep.target){
                    // Dep.target结果就是Watcher
                    dep.subs.push(Dep.target)
                }
                return value
            },
            set(val){
                value = val
                //当$data中某一个属性值发生变化，让Dep通知所有watcher更新页面
                dep.notify()
            }
        })
    }
    walk(){
        Object.keys(this.data).forEach(key=>{
            this.defineReative(this.data,key,this.data[key])
        })
    }
}

/**
 * 模板编译：将data中的数据和页面结合在一起。实现渲染过程
 */
class Compiler{
    //模板是谁，数据是哪一个
    //el="#app" data={title,msg}
    constructor(el,data){
        this.$el = document.querySelector(el)
        this.$data = data
        //接受到数据，初始化后。模板渲染
        this.compiler()
    }
    //开始将数据和模板结合起来渲染
    compiler(){
        //知道容器是谁，找到这个容器里面的所有子节点
        [...this.$el.children].forEach(item=>{
            // <p>{{msg}}</p>
            //判断哪些标签需要进行模板替换 h标签忽略 p标签替换模板
            if(/\{\{([a-zA-Z0-9_]+)\}\}/.test(item.innerHTML)){
                //key 获取值 msg title
                const key = RegExp.$1.trim()
                console.log("key",key);
                //操作虚拟dom
                const render = ()=>{
                    item.innerHTML = this.$data[key]
                }
                render()
                //每一个包含了{{}}标签，都要创建一个订阅者
                new Watcher(render)
            }
        })
    }
}

/**
 * 订阅者，负责通知页面更新
 */
class Watcher{
    constructor(callback){
        Dep.target = this
        this.callback = callback
        this.update()
        Dep.target = null
    }
    //更新页面
    update(){
        this.callback()
    }
}

/**
 * 负责管理所有订阅者，一旦数据发生变化，让订阅者更新页面
 */
class Dep{
    constructor(){
        // 存放订阅者（Watcher）
        this.subs = []
    }
    //通知watcher进行页面更新
    notify(){
        this.subs.forEach(watcher=>{
            watcher.update()
        })
    }
}




